home *** CD-ROM | disk | FTP | other *** search
/ CD ROM Paradise Collection 4 / CD ROM Paradise Collection 4 1995 Nov.iso / program / swagg_m.zip / MATH.SWG / 0074_FFT Algorithm in Pascal.pas < prev    next >
Pascal/Delphi Source File  |  1994-08-25  |  16KB  |  432 lines

  1. {
  2. ─ Area: U-PASCAL      |61 ────────────────────────────────────────────────────
  3.   Msg#: 5727                                         Date: 07-05-94  08:14
  4.   From: Bschor@vms.cis.pitt.edu                      Read: Yes    Replied: No
  5.     To: All                                          Mark:
  6.   Subj: FFT Algorithm in Pascal
  7. ──────────────────────────────────────────────────────────────────────────────
  8. From: bschor@vms.cis.pitt.edu
  9.  
  10.      Over the past several weeks, there have been questions about the Fast
  11. Fourier Transform, including requests for a version of the algorithm.  The
  12. following is one such implementation, optimized for clarity (??) at the
  13. possible expense of a few percentage points in speed (it's pretty darn
  14. fast).  It is written in "vanilla" Pascal, so it should work with all
  15. variants of the language.
  16.  
  17.      Note that buried in the comments is a reasonable reference for the
  18. algorithm.
  19.    }
  20.  
  21.  
  22. PROGRAM fft (input, output);
  23.  
  24.   {****************************************}
  25.   {                                        }
  26.   {         Bob Schor                      }
  27.   {         Eye and Ear Institute          }
  28.   {         203 Lothrop Street             }
  29.   {         Pittsburgh, PA   15213         }
  30.   {                                        }
  31.   {****************************************}
  32.  
  33.   { test routine for FFT in Pascal -- includes real and complex }
  34.  
  35.   { Version 1.6 -- first incarnation }
  36.   { Version 10.7 -- upgrade, allow in-place computation of coefficients }
  37.   { Version 14.6 -- comments added for didactic purposes }
  38.  
  39. CONST
  40.   version = 'FFT       Version 14.6';
  41.  
  42. CONST
  43.   maxarraysize = 128;
  44.   halfmaxsize = 64;
  45.   maxfreqsize = 63;
  46. TYPE
  47.   dataindextype = 1 .. maxarraysize;
  48.   cmpxindextype = 1 .. halfmaxsize;
  49.   freqindextype = 1 .. maxfreqsize;
  50.   complex = RECORD
  51.               realpart, imagpart : real
  52.             END;
  53.   dataarraytype = RECORD
  54.                     CASE (r, c) OF
  55.                       r : (rp : ARRAY [dataindextype] OF real);
  56.                       c : (cp : ARRAY [cmpxindextype] OF complex)
  57.                   END;
  58.   cstermtype = RECORD
  59.                  cosineterm, sineterm : real
  60.                END;
  61.   fouriertype = RECORD
  62.                   dcterm : real;
  63.                   noiseterm : real;
  64.                   freqterms : ARRAY [freqindextype] OF cstermtype
  65.                 END;
  66.   mixedtype = RECORD
  67.                 CASE (dtype, ctype) OF
  68.                   dtype : (dataslot : dataarraytype);
  69.                   ctype : (coefslot : fouriertype)
  70.               END;
  71.  
  72. CONST
  73.   twopi = 6.2831853;
  74. VAR
  75.   data : dataarraytype;
  76.   didx : dataindextype;
  77.   fidx : freqindextype;
  78.   coefficients : fouriertype;
  79.   mixed : mixedtype;
  80.  
  81.   { A note on declarations, above.  Pascal does not have a base type of
  82.    "complex", but it is fairly simple, given the strong typing in the
  83.    language, to define such a type.  One needs to write procedures (see
  84.    below) that implement the common arithmetic operators.  Functions
  85.    would be even better, from a logical standpoint, but the language
  86.    standard does not permit returning a record type from a function.
  87.    .     The FFT, strictly speaking, is a technique for transforming a
  88.    complex array of points-in-time into a complex array of points-in-
  89.    Fourier space (complex numbers that represent the gain and phase of
  90.    the response at discrete frequencies).  One typically has data,
  91.    representing samples taken at some fixed sampling rate, for which
  92.    one wants the Fourier transform, to compute a power spectrum, for
  93.    example.  Such data, of course, are "real" quantities.  One could
  94.    take these N points, make them the real part of a complex array of
  95.    size N (setting the imaginary part to zero), and take the FFT.
  96.    However, in the interest of speed (the first F of FFT means "fast",
  97.    after all), one can also do a trick where the N "real" points are
  98.    identified with the real, imaginary, real, imaginary, etc. points of
  99.    a complex array of size N/2.  The FFT now takes about half the time,
  100.    and one needs to do some final twiddling to obtain the sine/cosine
  101.    coefficients of the size N real array from the coefficients of the
  102.    size N/2 complex array.
  103.    .     To clarify the dual interpretation of the data array as either
  104.    N reals or N/2 complex points, the tagged type "dataarraytype" was
  105.    defined.  On input, it represents the complex data; on output from the
  106.    complex FFT, it represents the complex Fourier coefficients.  A final
  107.    transformation on these complex coefficients can convert them into a
  108.    series of real sine/cosine terms; for this purpose, the tagged type
  109.    "mixed" was defined for the real FFT.
  110.    .     Finally, note that this, and most, FFT routines get their
  111.    speed when the number of points is a power of 2.  This is because
  112.    the speed comes from a divide-and-conquer approach -- to do an FFT
  113.    of N points, do two FFTs of N/2 points and combine the results. }
  114.  
  115.  
  116.   PROCEDURE fftofreal (VAR mixed : mixedtype;
  117.                        realpoints : integer);
  118.  
  119.     { This routine performs a forward Fourier transform of an array
  120.      "mixed", which on input is assumed to consist of "realpoints" data
  121.      points and on output consists of a set of Fourier coefficients (a
  122.      DC term, (N/2 - 1) sine and cosine terms, and a residual "noise"
  123.      term). }
  124.  
  125.   CONST
  126.     twopi = 6.2831853;
  127.   VAR
  128.     index, minusindex : freqindextype;
  129.     temp1, temp2, temp3, w : complex;
  130.     baseangle : real;
  131.  
  132.     { The following procedures implement complex arithmetic -- }
  133.  
  134.     PROCEDURE cadd (a, b : complex;
  135.                     VAR c : complex);
  136.  
  137.       { c := a + b }
  138.  
  139.      BEGIN   { cadd }
  140.        WITH c DO
  141.         BEGIN
  142.           realpart := a.realpart + b.realpart;
  143.           imagpart := a.imagpart + b.imagpart
  144.         END
  145.      END;
  146.  
  147.     PROCEDURE csubtract (a, b : complex;
  148.                          VAR c : complex);
  149.  
  150.       { c := a - b }
  151.  
  152.      BEGIN   { csubtract }
  153.        WITH c DO
  154.         BEGIN
  155.           realpart := a.realpart - b.realpart;
  156.           imagpart := a.imagpart - b.imagpart
  157.         END
  158.      END;
  159.  
  160.     PROCEDURE cmultiply (a, b : complex;
  161.                          VAR c : complex);
  162.  
  163.       { c := a * b }
  164.  
  165.      BEGIN   { cmultiply }
  166.        WITH c DO
  167.         BEGIN
  168.           realpart := a.realpart*b.realpart - a.imagpart*b.imagpart;
  169.           imagpart := a.realpart*b.imagpart + b.realpart*a.imagpart
  170.         END
  171.      END;
  172.  
  173.     PROCEDURE conjugate (a : complex;
  174.                          VAR b : complex);
  175.  
  176.       { b := a* }
  177.  
  178.      BEGIN   { conjugate }
  179.        WITH b DO
  180.         BEGIN
  181.           realpart := a.realpart;
  182.           imagpart := -a.imagpart
  183.         END
  184.      END;
  185.  
  186.     PROCEDURE forwardfft (VAR data : dataarraytype;
  187.                           complexpoints : integer);
  188.  
  189.  
  190.       { The basic FFT is a recursive routine that basically works as
  191.        follows:
  192.        1)  The FFT is a linear operator, so the FFT of a sum is simply
  193.        .   the sum of the FFTs of each addend.
  194.        2)  The FFT of a time series shifted in time is the FFT of the
  195.        .   unshifted series adjusted by a twiddle factor which looks
  196.        .   like a (complex) root of 1 (an nth root of unity).
  197.        3)  Consider N points, equally spaced in time, for which you
  198.        .   want an FFT.  Start by splitting the series into odd and
  199.        .   even samples, giving you two series with N/2 points,
  200.        .   equally spaced, but with the second series delayed in time
  201.        .   by one sample.  Take the FFT of each series.  Using property
  202.        .   2), adjust the FFT of the second series for the time delay.
  203.        .   Now using property 1), since the original N points is simply
  204.        .   the sum of the two N/2 series, the FFT we want is simply the
  205.        .   sum of the FFTs of the two sub-series (with the adjustment
  206.        .   in the second for the time delay).
  207.        4)  This is essentially a recursive definition.  To do an N-point
  208.        .   FFT, do two N/2 point FFTs and combine the answers.  All we
  209.        .   need to stop the recursion is to know how to do a 2-point
  210.        .   FFT: if a and b are the two (complex) input points, the
  211.        .   two-point FFT equations are A := a+b; B := a-b.
  212.        5)  The FFT is rarely coded in its fully-recursive form.  It
  213.        .   turns out to be fairly simple to "unroll" the recursion and
  214.        .   reorder it a bit, which simplifies the computation of the
  215.        .   roots-of-unity complex twiddle factors.  The only drawback
  216.        .   is that the output array ends up scrambled -- if the array
  217.        .   indices are represented as going from 0 to M-1, then if one
  218.        .   represents the array index as a binary number, one needs to
  219.        .   bit-reverse the number to get the proper place in the array.
  220.        .   Thus, the next step is to swap values by bit-reversing the
  221.        .   indices.
  222.        6)  There are numerous references on the FFT.  A reasonable one
  223.        .   is "Numerical Recipes" by Press et al., Cambridge University
  224.        .   Press, which I believe exists in several language flavors. }
  225.  
  226.     CONST
  227.       twopi = 6.2831853;
  228.  
  229.       PROCEDURE docomplextransform;
  230.  
  231.       VAR
  232.         partitionsize, halfsize, offset,
  233.         lowindex, highindex : dataindextype;
  234.         baseangle, angle : real;
  235.         bits : integer;
  236.         w, temp : complex;
  237.  
  238.        BEGIN   { docomplextransform }
  239.          partitionsize := complexpoints;
  240.          WITH data DO
  241.           REPEAT
  242.            halfsize := partitionsize DIV 2;
  243.            baseangle := twopi/partitionsize;
  244.            FOR offset := 1 TO halfsize DO
  245.             BEGIN
  246.               angle := baseangle * pred(offset);
  247.               w.realpart := cos(angle);
  248.               w.imagpart := -sin(angle);
  249.               lowindex := offset;
  250.                REPEAT
  251.                 highindex := lowindex + halfsize;
  252.                 csubtract (cp[lowindex], cp[highindex], temp);
  253.                 cadd (cp[lowindex], cp[highindex], cp[lowindex]);
  254.                 cmultiply (temp, w, cp[highindex]);
  255.                 lowindex := lowindex + partitionsize
  256.                UNTIL lowindex >= complexpoints
  257.             END;
  258.            partitionsize := partitionsize DIV 2
  259.           UNTIL partitionsize = 1
  260.        END;
  261.  
  262.       PROCEDURE shufflecoefficients;
  263.  
  264.       VAR
  265.         lowindex, highindex : dataindextype;
  266.         bits : integer;
  267.  
  268.         FUNCTION log2 (index : integer) : integer;
  269.  
  270.           { Recursive routine, where "index" is assumed a power of 2.
  271.            Note the routine will fail (by endless recursion) if
  272.            "index" <= 0. }
  273.  
  274.          BEGIN   { log2 }
  275.            IF index = 1
  276.             THEN log2 := 0
  277.             ELSE log2 := succ(log2(index DIV 2))
  278.          END;
  279.  
  280.         FUNCTION bitreversal (index, bits : integer) : integer;
  281.  
  282.           { Takes an index, in the range 1 .. 2**bits, and computes a
  283.            bit-reversed index in the same range.  It first undoes the
  284.            offset of 1, bit-reverses the "bits"-bit binary number,
  285.            then redoes the offset.  Thus if bits = 4, the range is
  286.            1 .. 16, and bitreversal (1, 4) = 9,
  287.            bitreversal (16, 4) = 16, etc. }
  288.  
  289.           FUNCTION reverse (bits, stib, bitsleft : integer) : integer;
  290.  
  291.             { Recursive bit-reversing function, transforms "bits" into
  292.              bit-reversed "stib.  It's pretty easy to convert this to
  293.              an iterative form, but I think the recursive form is
  294.              easier to understand, and should entail a trivial penalty
  295.              in speed (in the overall algorithm). }
  296.  
  297.            BEGIN   { reverse }
  298.              IF bitsleft = 0
  299.               THEN reverse := stib
  300.               ELSE
  301.               IF odd (bits)
  302.                THEN reverse := reverse (bits DIV 2, succ (stib * 2),
  303.                                         pred (bitsleft))
  304.                ELSE reverse := reverse (bits DIV 2, stib * 2,
  305.                                         pred (bitsleft))
  306.            END;
  307.  
  308.          BEGIN   { bitreversal }
  309.            bitreversal := succ (reverse (pred(index), 0, bits))
  310.          END;
  311.  
  312.         PROCEDURE swap (VAR a, b : complex);
  313.  
  314.         VAR
  315.           temp : complex;
  316.  
  317.          BEGIN   { swap }
  318.            temp := a;
  319.            a := b;
  320.            b := temp
  321.          END;
  322.  
  323.        BEGIN   { shufflecoefficients }
  324.          bits := log2 (complexpoints);
  325.          WITH data DO
  326.          FOR lowindex := 1 TO complexpoints DO
  327.           BEGIN
  328.             highindex := bitreversal(lowindex, bits);
  329.             IF highindex > lowindex
  330.              THEN swap (cp[lowindex], cp[highindex])
  331.           END
  332.        END;
  333.  
  334.       PROCEDURE dividebyn;
  335.  
  336.       { This procedure is needed to get FFT to scale correctly. }
  337.  
  338.       VAR
  339.         index : dataindextype;
  340.  
  341.        BEGIN   { dividebyn }
  342.          WITH data DO
  343.          FOR index := 1 TO complexpoints DO
  344.          WITH cp[index] DO
  345.           BEGIN
  346.             realpart := realpart/complexpoints;
  347.             imagpart := imagpart/complexpoints
  348.  
  349.           END
  350.        END;
  351.  
  352.      BEGIN   { forwardfft }
  353.        docomplextransform;
  354.        shufflecoefficients;
  355.        dividebyn
  356.      END;
  357.  
  358.      { Note that the data slots and coefficient slots in the mixed
  359.      data type share storage.  From the first complex coefficient,
  360.      we can derive the DC and noise term; from pairs of the remaining
  361.      coefficients, we can derive pairs of sine/cosine terms. }
  362.  
  363.  
  364.    BEGIN   { fftofreal }
  365.      forwardfft (mixed.dataslot, realpoints DIV 2);
  366.      temp1 := mixed.dataslot.cp[1];
  367.      WITH mixed.coefslot, temp1 DO
  368.       BEGIN
  369.         dcterm := (realpart + imagpart)/2;
  370.         noiseterm := (realpart - imagpart)/2
  371.       END;
  372.      baseangle := -twopi/realpoints;
  373.      FOR index := 1 TO realpoints DIV 4 DO
  374.       BEGIN
  375.         minusindex := (realpoints DIV 2) - index;
  376.         WITH mixed.dataslot DO
  377.          BEGIN
  378.            conjugate (cp[succ(minusindex)], temp2);
  379.            cadd (cp[succ(index)], temp2, temp1);
  380.            csubtract (cp[succ(index)], temp2, temp2)
  381.          END;
  382.         w.realpart := sin(index*baseangle);
  383.         w.imagpart := -cos(index*baseangle);
  384.         cmultiply (w, temp2, temp2);
  385.         cadd (temp1, temp2, temp3);
  386.         csubtract (temp1, temp2, temp2);
  387.         conjugate (temp2, temp2);
  388.         WITH mixed.coefslot.freqterms[index], temp3 DO
  389.          BEGIN
  390.            cosineterm := realpart/2;
  391.            sineterm := -imagpart/2
  392.          END;
  393.         WITH mixed.coefslot.freqterms[minusindex], temp2 DO
  394.          BEGIN
  395.            cosineterm := realpart/2;
  396.            sineterm := imagpart/2
  397.          END
  398.       END
  399.    END;
  400.  
  401.   FUNCTION omegat (f : freqindextype; t : dataindextype) : real;
  402.  
  403.     { computes omega*t for particular harmonic, index }
  404.  
  405.    BEGIN   { omegat }
  406.      omegat := twopi * f * pred(t) / maxarraysize
  407.    END;
  408.  
  409.   { main test routine starts here }
  410.  
  411.  BEGIN
  412.    WITH mixed.dataslot DO
  413.    FOR didx := 1 TO maxarraysize DO
  414.    rp[didx] := (23
  415.                 + 13 * sin(omegat (7, didx))
  416.                 + 28 * cos(omegat (22, didx)));
  417.    fftofreal (mixed, maxarraysize);
  418.    WITH mixed.coefslot DO
  419.    writeln ('DC = ', dcterm:10:2, ' ':5, 'Noise = ', noiseterm:10:2);
  420.    FOR fidx := 1 TO maxfreqsize DO
  421.     BEGIN
  422.       WITH mixed.coefslot.freqterms[fidx] DO
  423.       write (fidx:4, round(cosineterm):4, round(sineterm):4, ' ':4);
  424.       IF fidx MOD 4 = 0
  425.        THEN writeln
  426.     END;
  427.    writeln;
  428.    writeln ('The expected result should have been:');
  429.    writeln ('  DC = 23, noise = 0, ');
  430.    writeln ('  sine 7th harmonic = 13, cosine 22nd harmonic = 28')
  431.  END.
  432.